package notification

import (
	"bytes"
	"context"
	"errors"
	"net/http"
	"strings"
	"time"

	"gorm.io/gorm"
)

type Notifier struct {
	// msgChan     chan *NotifierMessage
	ctx                context.Context
	serviceName        string
	validate           Validate
	db                 *gorm.DB
	interval           time.Duration
	defaultContentType string
	defaultMethod      string
}

type Validate func(*http.Response) error

type Options struct {
	ServiceName string
	Validate    //验证http应答是否合法，不合法的将轮询重试请求
	context.Context
	PollingInterval    time.Duration //轮询间隔
	DefaultContentType string
	DefaultMethod      string
}

func NewNotifier(db *gorm.DB, options Options) (*Notifier, error) {
	if db == nil {
		return nil, errors.New("require db")
	}
	//同步数据库结构
	if err := db.AutoMigrate(&NotifierMessage{}); err != nil {
		return nil, err
	}
	if options.Validate == nil {
		options.Validate = func(r *http.Response) error {
			if r.StatusCode >= 400 {
				return errors.New(http.StatusText(r.StatusCode))
			}
			return nil
		}
	}
	if options.Context == nil {
		options.Context = context.Background()
	}
	if options.PollingInterval == 0 {
		// options.PollingInterval = 1 * time.Second
		options.PollingInterval = 1 * time.Minute
	}
	if options.DefaultContentType == "" {
		options.DefaultContentType = "application/json"
	}
	if options.DefaultMethod == "" {
		options.DefaultMethod = "POST"
	}
	n := &Notifier{
		// msgChan:     make(chan *NotifierMessage, 100),
		ctx:                options.Context,
		serviceName:        options.ServiceName,
		validate:           options.Validate,
		db:                 db,
		interval:           options.PollingInterval,
		defaultContentType: options.DefaultContentType,
		defaultMethod:      options.DefaultMethod,
	}
	//开始轮询数据库发送未完成的消息
	go n.start()
	return n, nil
}

type NotifierMessage struct {
	ID          string     `gorm:"primaryKey; type:varchar(255);"`
	ServiceName string     `gorm:"index:idx_retry; type:varchar(255);"`
	SuccessAt   *time.Time `gorm:"index:idx_retry"`
	Message     `gorm:"embedded"`
	FailCount   int `gorm:"type:int(11);"`
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

// TableName overrides the table name used by Message to `yi_notifier_messages`
func (NotifierMessage) TableName() string {
	return "yi_notifier_messages"
}

// type HttpMethod string

// const (
// 	GET    HttpMethod = "GET"
// 	POST   HttpMethod = "POST"
// 	PUT    HttpMethod = "PUT"
// 	DELETE HttpMethod = "DELETE"
// )

type Message struct {
	ExpiredAt   time.Time `gorm:"not null; index:idx_retry;"`
	Hosts       string    `gorm:"not null; type:varchar(255);"`
	Path        string    `gorm:"not null; type:varchar(255);"`
	Method      string    `gorm:"type:varchar(255);"` //默认使用POST
	ContentType string    `gorm:"type:varchar(255);"` //默认使用application/json
	Data        []byte
}

// var defaultContentType = "application/json"
// var defaultMethod = "POST"

func (n *Notifier) NewNotifierMessage(message Message) (*NotifierMessage, error) {
	//简单验证，懒
	if message.Hosts == "" || message.Path == "" {
		return nil, errors.New("require Hosts and Path")
	}
	if message.ContentType == "" {
		// contentType := http.DetectContentType(message.Data)
		message.ContentType = n.defaultContentType
	}
	if message.Method == "" {
		message.Method = n.defaultMethod
	}
	if message.ExpiredAt == (time.Time{}) {
		message.ExpiredAt = time.Now().AddDate(0, 0, 2)
	}
	return &NotifierMessage{
		ID:          NewULID().String(),
		ServiceName: n.serviceName,
		Message:     message,
	}, nil
}

//通知消息
func (n *Notifier) Notify(m *NotifierMessage) (*http.Response, error) {
	res, err := n.notify(m)
	if err != nil {
		//出错或不合法就放到轮询队列
		//先查询有没有记录数据库
		tx := n.db.First(m, "id = ?", m.ID)
		if tx.Error != nil {
			return nil, tx.Error
		}
		// n.msgChan <- m
		return res, err
	}
	return res, nil
}

func (n *Notifier) notify(m *NotifierMessage) (*http.Response, error) {
	if m == nil {
		return nil, errors.New("require notifierMessage")
	}
	if m.ID == "" {
		return nil, errors.New("require ID")
	}
	if m.ContentType == "" {
		m.ContentType = n.defaultContentType
	}
	if m.Method == "" {
		m.Method = n.defaultMethod
	}
	m.ServiceName = n.serviceName
	hosts := strings.Split(m.Hosts, ",")
	path := m.Path
	body := bytes.NewReader(m.Data)
	req, err := http.NewRequest(m.Method, path, body)
	req.Header.Set("content-type", m.ContentType)
	if err != nil {
		return nil, err
	}
	res, err := Do(hosts, req)
	if err == nil {
		err = n.validate(res)
	}
	if res != nil && res.Body != nil {
		res.Body.Close()
	}
	if err == nil {
		//记录成功
		now := time.Now()
		err = n.db.Model(&m).Updates(NotifierMessage{SuccessAt: &now}).Error
	} else {
		err = n.db.Model(&m).Update("fail_count", gorm.Expr("fail_count + ?", 1)).Error
	}
	return res, err
}

// const interval = 1 * time.Second

// const interval = 1 * time.Minute

// const expiration = 24 * 2 * time.Hour
// const max_fail_count = int(expiration / interval)

// const max_fail_count = 10

func (n *Notifier) start() {
	for {
		select {
		case <-n.ctx.Done():
			return
		case <-time.After(n.interval):
			list := []NotifierMessage{}
			n.db.Where("service_name = ? AND success_at is null AND expired_at > ?", n.serviceName, time.Now()).Limit(1000).Find(&list)
			// log.Println(len(list))
			for _, m := range list {
				n.notify(&m)
			}
		}
	}
}
